#include "favicongrabber.h"

#include <QFileInfo>
#include <QSettings>
#include <QDir>
#include <QApplication>

#include <QtWebKit/QWebPage>
#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebElement>

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>


QMap<QString, QIcon> FavIconGrabber::m_cache;

FavIconGrabber *FavIconGrabber::grab(const QUrl &url)
{
    return new FavIconGrabber(url);
}

FavIconGrabber::FavIconGrabber(const QUrl &url)
:   m_url(url)
,   m_asyncGetPixmapFails(0)
,   m_page(NULL)
,   m_networkAccessManager(NULL)
{
}

FavIconGrabber::~FavIconGrabber()
{
    delete m_page;

    if(m_networkAccessManager)
        m_networkAccessManager->deleteLater();
}

void FavIconGrabber::start()
{
    if(loadFromCache())
    {
        emit grabSucceeded(m_icon);
        return;
    }
    if(loadFromFile())
    {
        succeeded();
        return;
    }
    startAsyncParse();
}

void FavIconGrabber::cache()
{
    m_cache[m_url.host()] = m_icon;
}

void FavIconGrabber::succeeded()
{
    cache();
    emit grabSucceeded(m_icon);
}

void FavIconGrabber::failed()
{
    m_icon = QIcon(":/favicon");

    cache();
    emit grabFailed(m_icon);
}

const bool FavIconGrabber::loadFromCache()
{
    if(m_cache.contains(m_url.host()))
        m_icon = m_cache[m_url.host()];

    return m_cache.contains(m_url.host());
}

const bool FavIconGrabber::loadFromFile()
{
    const QFileInfo fi(fileName(m_url));

    if(!fi.exists())
        return false;

    QIcon ico(fi.absoluteFilePath());

    if(ico.isNull())
        return false;

    m_icon = ico;
    return true;
}


// async stuff

void FavIconGrabber::startAsyncParse()
{
    // try two async gets simultaneously

    asyncGetWebPage();

    //directAsyncGet();
}

void FavIconGrabber::checkAndActIfFuckedUp()
{
    if(m_asyncGetPixmapFails > 1)
        failed();
}

// async web page

void FavIconGrabber::asyncGetWebPage()
{
    m_page = new QWebPage();

    QWebFrame *frame = m_page->mainFrame();

    connect(frame, SIGNAL(loadFinished(bool))
        , this, SLOT(me_getWebPage_succeeded(bool)));

    frame->load(m_url);
}

void FavIconGrabber::me_getWebPage_succeeded(bool ok)
{
    if(!ok)
    {
        me_getWebPage_failed();
        return;
    }

    const QString href(parsePageIconHRef(m_page));

    if(!href.isEmpty())
        asyncGetPixmap(href);
    else
        me_getWebPage_failed();
}

void FavIconGrabber::me_getWebPage_failed()
{
    ++m_asyncGetPixmapFails;
    checkAndActIfFuckedUp();

    directAsyncGrab();
}

void FavIconGrabber::directAsyncGrab()
{
    const QString href = QString("%1://%2/favicon.ico")
        .arg(m_url.scheme()).arg(m_url.host());

    asyncGetPixmap(href);
}


// async pixmap

void FavIconGrabber::asyncGetPixmap(const QString &href)
{
    m_networkAccessManager = new QNetworkAccessManager();
    QNetworkRequest request(href);

    connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*))
        , this, SLOT(me_getBytes_succeeded(QNetworkReply*)));


    connect(m_networkAccessManager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*))
        , this, SLOT(me_getBytes_failed()));

    connect(m_networkAccessManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&))
        , this, SLOT(me_getBytes_failed()));


    m_networkAccessManager->get(request);
}

void FavIconGrabber::me_getBytes_succeeded(QNetworkReply *reply)
{
    if(!reply->isReadable())
        me_getBytes_failed();

    const QByteArray bytes = reply->readAll();

    QPixmap pixmap;
    pixmap.loadFromData(bytes);

    if(pixmap.isNull())
    {
        me_getBytes_failed();
        return;
    }

    m_icon = QIcon(pixmap);
    pixmap.save(fileName(m_url));

    succeeded();

    reply->deleteLater();
}

void FavIconGrabber::me_getBytes_failed()
{
    ++m_asyncGetPixmapFails;

    if(1 == m_asyncGetPixmapFails)
        directAsyncGrab();
    else
        checkAndActIfFuckedUp();
}


const QString FavIconGrabber::fileName(const QUrl &url)
{
    QSettings pseudo(
        QSettings::IniFormat
    ,   QSettings::UserScope
    ,   QCoreApplication::organizationName()
    ,   QCoreApplication::applicationName());

    QDir userAppDataDir = QFileInfo(pseudo.fileName()).dir();

    const QString favicon_cache = QString("%1/%2")
        .arg(QCoreApplication::applicationName())
        .arg("favicon_cache");

    if(!userAppDataDir.exists(favicon_cache))
        userAppDataDir.mkpath(favicon_cache);

    return QString("%1/%2/%3.ico")
        .arg(userAppDataDir.absolutePath())
        .arg(favicon_cache)
        .arg(url.host());
}

const QString FavIconGrabber::parsePageIconHRef(QWebPage *page)
{
    QString href;

    if(page)
    {
        QWebElement document = page->mainFrame()->documentElement();
        QWebElement e = document.findFirst("link[rel*=\"icon\"]");

        if(!e.isNull() && e.hasAttribute("href"))
        {
            QUrl test(e.attribute("href"));
            QUrl url = page->mainFrame()->url();

            if(test.isRelative())
                href = QString("%1://%2/%3")
                    .arg(url.scheme()).arg(url.host()).arg(e.attribute("href"));
            else
                href = test.toString();
        }
    }
    return href;
}
